package com.smarthome.api.mobile;

import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.Date;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.format.datetime.DateFormatter;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.JSONSerializer;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializeWriter;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.alibaba.fastjson.serializer.SimpleDateFormatSerializer;
import com.alibaba.fastjson.serializer.ValueFilter;
import com.smarthome.common.mobile.Service;
import com.smarthome.common.tools.ApiCachedKeysUtil;
import com.smarthome.common.tools.SpringUtils;
import com.smarthome.common.tools.cache.JedisUtils;
import com.smarthome.core.account.AccountService;
import com.smarthome.core.workingmonitor.AccountLogService;
import com.smarthome.domain.bean.ShAccount;
import com.smarthome.domain.bean.ShAccountLog;
import com.smarthome.domain.enums.AccountLogTypeEnum;
import com.smarthome.domain.mobile.MobileCodeEnum;
import com.smarthome.domain.mobile.MobileConstants;
import com.smarthome.domain.mobile.MobileRequest;
import com.smarthome.domain.mobile.MobileResponse;

@org.springframework.stereotype.Service("mobileServiceExporter")
public class ServiceExporter {

    private static final Logger LOGGER = LoggerFactory.getLogger(ServiceExporter.class);
    private static final DateFormatter TIMESTAMP = new DateFormatter(MobileConstants.TIMESTAMP_FORMAT);
    private static final DateFormatter DATE = new DateFormatter(MobileConstants.DATE_FORMAT);
    @Resource
    private AccountLogService accountLogService;
    @Resource
    private AccountService accountService;

    public MobileResponse<?> doService(Map<String, String> params) throws Exception {
        MobileResponse<?> response = new MobileResponse();
        String serviceName = params.get(MobileConstants.MOBILE_SERVICE_NAME);
        String methodName = params.get(MobileConstants.MOBILE_SERVICE_METHOD_NAME);
        Service<?> service = getService(serviceName);
        Method serviceMethod = getMethod(service, methodName);
        if (serviceMethod == null) {
            response.setMobileCodeObj(MobileCodeEnum.COMMAND_NOT_FOUND);
            return response;
        }
        Class<?>[] parameterTypes = serviceMethod.getParameterTypes();
        MobileRequest request = (MobileRequest) parameterTypes[0].newInstance();
        bind(params, request);
        response = (MobileResponse<?>) parameterTypes[1].newInstance();
        if (request.isLogined()) {
            if (StringUtils.isBlank(request.getLoginToken())) {
                response.setMobileCodeObj(MobileCodeEnum.ACCOUNT_NOT_FULL);
                return response;
            } else {
                ShAccount account = JedisUtils.get(ApiCachedKeysUtil.getKey(ApiCachedKeysUtil.MODULE_MOBILE_TOKEN, request.getLoginToken()), ShAccount.class);
                if (null == account) {
                    response.setMobileCodeObj(MobileCodeEnum.ACCOUNT_LOGIN_NEED);
                    return response;
                }
                request.setAccount(account);
                request.setAccountId(account.getId());
            }
        }
        try {
            ShAccountLog accountLog = new ShAccountLog();
            accountLog.setAccountId(request.getAccountId());
            accountLog.setResidenceId(1L);
            accountLog.setTypeId(AccountLogTypeEnum.OPERATE.getCode());
            accountLog.setContent("调用" + serviceName + "-" + methodName);
            accountLogService.saveLog(accountLog);
            if (null != request.getAccountId() && request.getAccountId() > 0) {
                ShAccount account = new ShAccount();
                account.setId(request.getAccountId());
                account.setLastLoginTime(new Date());
                accountService.edit(account);
            }
            serviceMethod.invoke(service, request, response);
        } catch (Exception e) {
            LOGGER.error("operate-error:", e);
            response.setMobileCodeObj(MobileCodeEnum.SYSTEM_ERROR);
        }
        return response;
    }

    private static Service<?> getService(String serviceName) {
        if (StringUtils.isNotBlank(serviceName)) {
            return SpringUtils.getBean(serviceName);
        }
        return null;
    }

    private static Method getMethod(Service<?> service, String methodName) {
        if (service == null) {
            return null;
        }
        Method serviceMethod = null;
        try {
            for (Method method : service.getClass().getDeclaredMethods()) {
                if (method.getName().equals(methodName)) {
                    serviceMethod = method;
                    break;
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return serviceMethod;
    }

    private static void bind(Map<String, String> params, Object obj) throws Exception {
        for (Entry<String, String> entry : params.entrySet()) {
            try {
                setValue(obj, getField(obj.getClass(), entry.getKey()), entry.getValue());
            } catch (NoSuchFieldException e) {
                // ignore the exception
            }
        }
    }

    private static void setValue(Object obj, Field field, String value) throws Exception {
        if (field == null || StringUtils.isBlank(value)) {
            return;
        }
        field.setAccessible(true);
        Class<?> valueType = field.getType();
        if (valueType == Byte.class || valueType == byte.class) {
            field.set(obj, Byte.valueOf(value));
        } else if (valueType == Short.class || valueType == short.class) {
            field.set(obj, Short.valueOf(value));
        } else if (valueType == Integer.class || valueType == int.class) {
            field.set(obj, Integer.valueOf(value));
        } else if (valueType == Float.class || valueType == float.class) {
            field.set(obj, Float.valueOf(value));
        } else if (valueType == Double.class || valueType == double.class) {
            field.set(obj, Double.valueOf(value));
        } else if (valueType == Long.class || valueType == long.class) {
            field.set(obj, Long.valueOf(value));
        } else if (valueType == Boolean.class || valueType == boolean.class) {
            field.set(obj, Boolean.parseBoolean(value));
        } else if (valueType == String.class) {
            field.set(obj, value);
        } else if (valueType == Date.class) {
            if (MobileConstants.TIMESTAMP_FORMAT.length() == value.length()) {
                field.set(obj, TIMESTAMP.parse(value, Locale.CHINESE));
            } else if (MobileConstants.DATE_FORMAT.length() == value.length()) {
                field.set(obj, DATE.parse(value, Locale.CHINESE));
            } else if (!StringUtils.containsIgnoreCase(value, "-")) { // 非格式化 日期参数
                try {
                    Long times = Long.valueOf(StringUtils.trim(value));
                    Date date = new Date(times);
                    field.set(obj, date);
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new RuntimeException(e);
                }
            } else {
                throw new RuntimeException("不支持的日期格式");
            }
        } else if (valueType == BigDecimal.class) {
            field.set(obj, new BigDecimal(value));
        }
    }

    private static Field getField(Class<?> clz, String fieldName) {
        if (clz == null || StringUtils.isBlank(fieldName)) {
            return null;
        }
        Field field = null;
        try {
            field = clz.getDeclaredField(fieldName);
        } catch (Exception ex) {
            // ignore the exception
        }
        return field == null ? getField(clz.getSuperclass(), fieldName) : field;
    }

    public String parseJsonString(MobileResponse<?> response, String dateFormat) {
        dateFormat = dateFormat == null ? MobileConstants.TIMESTAMP_FORMAT : dateFormat;
        SerializeConfig config = new SerializeConfig();
        config.put(Date.class, new SimpleDateFormatSerializer(dateFormat));
        SerializerFeature[] features = { SerializerFeature.WriteNullNumberAsZero, SerializerFeature.WriteMapNullValue, SerializerFeature.WriteNullStringAsEmpty,
                SerializerFeature.DisableCircularReferenceDetect };
        return JSONObject.toJSONString(response, config, features);
    }

    public void sendResponse(String content, HttpServletResponse response) throws IOException {
        response.setContentType("text/javascript;charset=UTF-8");
        // byte[] responseBytes = content.getBytes("UTF-8");
        // response.setContentLength(responseBytes.length);
        PrintWriter out = response.getWriter();
        out.write(content);
        out.flush();
        // response.getWriter().write(content);
        // response.flushBuffer();
    }

    private static final String toJSONString(Object object, String timeFormat, SerializerFeature... features) {
        SerializeWriter out = new SerializeWriter();
        try {
            JSONSerializer serializer = new JSONSerializer(out);
            for (com.alibaba.fastjson.serializer.SerializerFeature feature : features) {
                serializer.config(feature, true);
            }
            serializer.config(SerializerFeature.WriteDateUseDateFormat, true);
            if (StringUtils.isNotBlank(timeFormat)) {
                serializer.setDateFormat(timeFormat);
            }
            serializer.getValueFilters().add(new ValueFilter() {

                @Override
                public Object process(Object object, String name, Object value) {
                    if (value != null) {
                        return value;
                    }
                    try {
                        Field field = object.getClass().getDeclaredField(name);
                        field.setAccessible(true);
                        Class<?> fieldType = field.getType();
                        if (fieldType.isAssignableFrom(String.class)) {
                            return "";
                        } else if (fieldType.isAssignableFrom(Boolean.class)) {
                            return Boolean.FALSE;
                        }
                    } catch (Exception e) {
                        // ignore the exception
                    }
                    return null;
                }
            });
            serializer.write(object);
            return out.toString();
        } finally {
            out.close();
        }
    }
}
